#include <iostream>

#if 0
    // C语言里，堆上空间，用户手动申请，手动释放
    // 但C++由于其构造函数与析构函数都是自动调用的方式
    // 所以就有这样一种写法：在构造里申请空间，在析构里释放
    class Test
    {
    public:
        Test()
        {
            std::cout<<"构造函数!\n";
            a = new int;
        }

        ~Test()
        {
            std::cout<<"析构函数!\n";
            delete a;
        }
    private:
        int *a; // 写代码的时候不会在一个类里面定的全是对象、数组，难免会在堆上申请空间，所以它的成员变量会是一个指针，这就涉及到空间的申请和释放
    };

    int main()
    {
        Test t;   
        return 0;
    }
#endif

#if 0
    // 浅拷贝问题
    // 会发生在两个函数身上，一个是拷贝构造，一个是赋值运算符重载
    class Test
    {
    public:
        Test()
        {
            std::cout<<"构造函数!\n";
            a = new int;
        }

        ~Test()
        {
            std::cout<<"析构函数!\n";
            delete a;
        }
    private:
        int *a; 
    };

    int main()
    {
        Test t;
        Test t1 = t;    // 拷贝构造，会造成二次释放问题（首先，构造一个t，它里面有指针a，构造时给它在堆上申请了一块空间，然后又构造了一个t1，用t拷贝给它，拷贝的时候默认把t里面的a也拷贝过来了，由于a是一个指针，那么拷贝过来的是它内存里放的地址，那就意味着这个t1里的a也指向同一块内存空间，所以当析构对象t的时候，a会被释放一次，当析构t1的时候，a又会被释放一次，这就是典型的浅拷贝问题）   
        return 0;
    }
#endif

#if 0
    // 深拷贝问题
    // 指针成员不能简单地进行地址拷贝，要进行内存拷贝
    class Test
    {
    public:
        Test()
        {
            std::cout<<"构造函数!\n";
            a = new int;
        }

        // 拷贝构造
        Test(Test &t)
        {
            std::cout<<"拷贝构造函数!\n";
            // a = t.a;     // 不能简单地就这样写，这样还是浅拷贝
            a = new int;    // 1、要先申请空间
            *a = *t.a;      // 2、然后把它内存的值拷过来，这样才可以
        }

        ~Test()
        {
            std::cout<<"析构函数!\n";
            delete a;
        }
    private:
        int *a; 
    };

    int main()
    {
        Test t;
        Test t1 = t;    
        return 0;
    }
#endif

#if 0
    // 赋值运算符重载（不算构造，就是一个函数）
    class Test
    {
    public:
        Test()
        {
            std::cout<<"构造函数!\n";
            a = new int;
        }

        // 拷贝构造
        Test(Test &t)
        {
            std::cout<<"拷贝构造函数!\n";
            // a = t.a;     
            a = new int;    
            *a = *t.a;    
        }

        // void operator=(Test &t)
        // {
        //     std::cout<<"赋值运算符重载！\n";
        // }

        void operator=(Test &t)
        {
            std::cout<<"赋值运算符重载！\n";
#if 0
            // 所以对于深拷贝的拷贝构造和赋值运算符重载来说，赋值运算符重载要多一步释放a一开始指向的那块空间
            delete a;       // 1、先delete原来的
#endif
            // 如果就这样写赋值运算符重载，它会有内存泄漏
            // 因为t在构造时new了个int，t1在构造时也new了个int，然后把t给到t1的时候，直接把这个a指向了一块新的int，但是原来的int还没有放掉，所以漏了一块内存
            a = new int;    // 2、申请新的空间
            *a = *t.a;      // 3、把值赋过来
        }
        
        ~Test()
        {
            std::cout<<"析构函数!\n";
            delete a;
        }
    private:
        int *a; 
    };

    int main()
    {
        Test t;
        Test t1;
        t1 = t; // 赋值运算符重载（把t的值赋给t1）
        // 如果把上面写的赋值运算符重载注释掉，不代表赋值运算符重载函数不存在，编译器会给一个默认的缺省的赋值运算符重载函数，所以它还是存在，代码依旧能编过能运行
        // 但会出现二次释放问题（类里面但凡出现二次释放都是浅拷贝问题，浅拷贝问题不止出现在拷贝构造身上，赋值运算符重载如果不写，让它缺省地生成，那在赋值的时候还是拷贝，到时候又指向同一块内存，又造成二次释放）
        return 0;
    }
#endif

#if 0
    // 匿名对象
    class Test
    {
    public:
        Test()
        {
            std::cout<<"构造函数!\n";
            a = new int;
        }

        // 拷贝构造
        Test(Test &t)
        {
            std::cout<<"拷贝构造函数!\n";
            // a = t.a;     
            a = new int;    
            *a = *t.a;    
        }

        void operator=(Test &t)
        {
            std::cout<<"赋值运算符重载！\n";
            delete a;       // 1、先delete原来的
            a = new int;    // 2、申请新的空间
            *a = *t.a;      // 3、把值赋过来
        }
        
        ~Test()
        {
            std::cout<<"析构函数!\n";
            delete a;
        }
    private:
        int *a; 
    };

    int main()
    {
        // 匿名对象（构建了一个对象，但这个对象没有名字）
        // 特点：生命周期只有一行，一个被生成出来马上就会被释放的对象
        // 匿名对象也是一个右值，对于这样一个对象，有内存，但是这个内存马上就会被释放，像这种值我们称为 将亡值
        // 将亡值也是右值
        // 凡是右值，是能够被拿走内存的
        // Test(); 
        // while(1); 

        // Test t;
        // Test t1 = t;      // 对左值进行拷贝构造
        Test t1 = Test();   // 匿名对象，这句话就不会发生拷贝构造了
        // 流程是这样的：首先右边先构建了个Test()对象，它有内存，但是这个对象内存没有名字，因为它是匿名对象，马上要被干掉了，这时候要构建一个对象t1要等于这块内存，正好这块内存没有名字，于是就直接拿过来，粘上t1的名字，所以最后没有发生拷贝
        // 所以这句话只会构造一个对象
        // 基于这个特性，可以对函数进行优化，如下例
        return 0;   
    }
#endif

#if 0
    // 优化函数
    class Test
    {
    public:
        Test()
        {
            std::cout<<"构造函数!\n";
            a = new int;
        }

        // 拷贝构造
        Test(Test &t)
        {
            std::cout<<"拷贝构造函数!\n";
            // a = t.a;     
            a = new int;    
            *a = *t.a;    
        }

        void operator=(Test &t)
        {
            std::cout<<"赋值运算符重载！\n";
            delete a;       // 1、先delete原来的
            a = new int;    // 2、申请新的空间
            *a = *t.a;      // 3、把值赋过来
        }
        
        ~Test()
        {
            std::cout<<"析构函数!\n";
            delete a;
        }
    private:
        int *a; 
    };

    // RVO优化：返回值优化（当返回的是一个局部变量的时候，会被优化掉）
    // 返回局部对象不会发生拷贝，而是直接夺取内存（占领它的内存，构建我的对象），省了一次拷贝构造
    // 左值是不会优化的，只有右值才能被优化（因为左值是有名字有内存的变量，有主）
    Test func()
    {
        Test t;     // 构建了个局部变量t
        std::cout<<"hello\n";
        return t;   // 将局部变量t进行返回 将亡值
    }

    int main()
    {
        // 正常来说这个函数将对象t返回给t1，应该要调拷贝构造 
        // 但是输出结果只有一次构造，构造的是t
        // 首先t先构造，构造出来将其返回给t1，正常来说是把t的内存拷过来，但由于t是个局部变量，在返回的时候意味着内存要没了，也就是将亡值，那就直接把它的内存拿过来进行构造t1，所以省去了一次拷贝构造的过程，也就是省去了一次构建对象的过程，这个东西称之为RVO优化
        Test t1 = func();   
        return 0;   
    }
#endif

#if 0
    // 优化函数
    class Test
    {
    public:
        Test()
        {
            std::cout<<"构造函数!\n";
            a = new int;
        }

        // 拷贝构造
        Test(Test &t)
        {
            std::cout<<"拷贝构造函数!\n";
            // a = t.a;     
            a = new int;    
            *a = *t.a;    
        }

        void operator=(Test &t)
        {
            std::cout<<"赋值运算符重载！\n";
            delete a;       // 1、先delete原来的
            a = new int;    // 2、申请新的空间
            *a = *t.a;      // 3、把值赋过来
        }
        
        ~Test()
        {
            std::cout<<"析构函数!\n";
            delete a;
        }
    private:
        int *a; 
    };

    Test func()
    {
        // 这样就会拷贝了
        static Test t;  // t是左值，不会被优化       
        return t;   
    }

    int main()
    {
        Test t1 = func();   
        return 0;   
    }
#endif

#if 0
    // 优化函数
    class Test
    {
    public:
        Test()
        {
            std::cout<<"构造函数!\n";
            a = new int;
        }

        // 拷贝构造
        Test(Test &t)
        {
            std::cout<<"拷贝构造函数!\n";
            // a = t.a;     
            a = new int;    
            *a = *t.a;    
        }

        void operator=(Test &t)
        {
            std::cout<<"赋值运算符重载！\n";
            delete a;       // 1、先delete原来的
            a = new int;    // 2、申请新的空间
            *a = *t.a;      // 3、把值赋过来
        }
        
        ~Test()
        {
            std::cout<<"析构函数!\n";
            delete a;
        }
    private:
        int *a; 
    };

    // 这个t2虽然在函数栈上，但不是诞生在函数栈上的，不能算是真正的内部变量，它虽然是个形参，是个局部变量，但是和下面的t是有区别的，所以RVO优化轮不到它  
    Test func(Test t2)  // 这里如果传引用，那就只发生一次拷贝，因为t2就是t传的引用，所以不会发生传参的一次拷贝
    {
        // Test t;     // 这个t是真正诞生在函数栈上，然后返回的，它是将亡值，是会被RVO优化掉的 
        return t2;   
    }

    int main()
    {
        Test t; // 打印出来第一个构造函数是t
        Test t1 = func(t);  // 函数调用的时候，实参t传给形参t2发生了第一次拷贝，返回给t1的时候发生了第二次拷贝  
        return 0;   // 由于main函数还没结束，最先析构的是t2，因为拷贝完就没有意义了，最后main函数结束，t和t1一起走
    }
#endif

#if 1
    // 优化函数
    class Test
    {
    public:
        Test()
        {
            std::cout<<"构造函数!\n";
            a = new int;
        }

        // 拷贝构造
        Test(Test &t)
        {
            std::cout<<"拷贝构造函数!\n";
            // a = t.a;     
            a = new int;    
            *a = *t.a;    
        }

        void operator=(Test &t)
        {
            std::cout<<"赋值运算符重载！\n";
            delete a;       // 1、先delete原来的
            a = new int;    // 2、申请新的空间
            *a = *t.a;      // 3、把值赋过来
        }
        
        ~Test()
        {
            std::cout<<"析构函数!\n";
            delete a;
        }
    private:
        int *a; 
    };

    Test func()  
    {
        Test t;     // 这个t是真正诞生在函数栈上，然后返回的，它是将亡值，和上面的形参传参还是有区别的
        return t;   // 这样一个局部变量会被优化掉，会将这个将亡值的内存直接给到t1   
    }

    int main()
    { 
        Test t1 = func();  
        return 0;  
    }
    /* 总结：直接诞生在系统函数栈上的局部变量，返回时会被优化，减少拷贝
            而形参列表中的变量和引用传递的左值变量都不会被优化 */
#endif
